﻿using System;
using System.Collections.Generic;

namespace Jayden.Dll.Tetris
{
	public class GameModel
	{
		private int m_Width = 10;
		private int m_Height = 20;
		private int m_Lines = 0;
		private int m_Level = 0;
		private int m_Score = 0;
		private List<BlockDefinition> m_Blocks = new List<BlockDefinition>(new BlockDefinition[] { BlockDefinition.I, BlockDefinition.J, BlockDefinition.L, BlockDefinition.O, BlockDefinition.S, BlockDefinition.T, BlockDefinition.Z });

		private Random m_Random;
		private Block[] m_Matrix;

		private BlockDefinition m_NextBlock;
		private Block m_Block;

		private void Reset()
		{
			m_Lines = 0;
			m_Level = 0;
			m_Score = 0;
			m_Random = new Random();
			m_Block = null;
			m_NextBlock = null;
			m_Matrix = new Block[m_Width * m_Height];
		}

		private void PlaceBlock()
		{
			Block block = m_Block;
			m_Block = null;
		}

		private bool CanFit(int x, int y, int orientation)
		{
			if (x < 0)
				return false;
			int previousX = m_Block.X;
			int previousY = m_Block.Y;
			int previousOrientation = m_Block.Orientation;
			m_Block.X = x;
			m_Block.Y = y;
			m_Block.Orientation = orientation;
			bool result = true;
			try
			{
				if (m_Block.Width + x > m_Width)
					result = false;
				if (m_Block.Height + y > m_Height)
					result = false;
				for(int c=0;result && c<m_Block.Width;c++)
					for(int d=0;result && d<m_Block.Height;d++)
						if (m_Block.IsBlock(x + c, y + d))
						{
							int offset = (x + c) + (y + d) * m_Width;
							if (m_Matrix[offset] != null)
								result = false;
						}
			}
			finally
			{
				m_Block.X = previousX;
				m_Block.Y = previousY;
				m_Block.Orientation = previousOrientation;
			}
			return result;
		}

		public int Width { get { return m_Width; } }
		public int Height { get { return m_Height; } }
		public int Score { get { return m_Score; } }
		public int Level { get { return m_Level; } }

		public void MoveRight()
		{
			if (CanFit(m_Block.X + 1, m_Block.Y, m_Block.Orientation))
				m_Block.X++;
		}
		public void MoveLeft()
		{
			if (CanFit(m_Block.X - 1, m_Block.Y, m_Block.Orientation))
				m_Block.X--;
		}
		public void MoveDown()
		{
			if (CanFit(m_Block.X, m_Block.Y + 1, m_Block.Orientation))
				m_Block.Y++;
			else
			{
				SetBlock();
				CheckLines(m_Block.Y, m_Block.Y + m_Block.Height);
				m_Block = null;
				NextBlock();
			}
		}
		public void RotateRight()
		{
			if (CanFit(m_Block.X, m_Block.Y, m_Block.RotateRight()))
				m_Block.Orientation = m_Block.RotateRight();
		}

		private void SetBlock()
		{
			for (int c = 0; c < m_Block.Width; c++)
				for (int d = 0; d < m_Block.Height; d++)
					if (m_Block.IsBlock(m_Block.X + c, m_Block.Y + d))
					{
						int offset = (m_Block.X + c) + (m_Block.Y + d) * m_Width;
						m_Matrix[offset] = m_Block;
					}
		}
		private void NextBlock()
		{
			m_Block = new Block(m_NextBlock);
			NextRandomBlock();
			m_Block.X = (m_Width - m_Block.Width) / 2;
			m_Block.Y = 0;
		}
		private void NextRandomBlock()
		{
			m_NextBlock = m_Blocks[m_Random.Next(m_Blocks.Count)];
		}
		public void Start()
		{
			Reset();
			NextRandomBlock();
			NextBlock();
		}

		public Block Get(int x, int y)
		{
			if(m_Block.IsBlock(x, y))
				return m_Block;
			if (x < 0 || y < 0 || x >= m_Width || y >= m_Height)
				return null;
			int offset = x + y * m_Width;
			return m_Matrix[offset];
		}

		private void CheckLines(int y, int max)
		{
			int linesCount = 0;
			for (int c = y; c < max; c++)
			{
				int baseOffset = c * m_Width;
				bool fullLine = true;
				for (int d = 0; fullLine && d < Width; d++)
					if (m_Matrix[baseOffset + d] == null)
						fullLine = false;
				if (fullLine)
				{
					linesCount++;
					FallOneLine(c);
					m_Lines++;
				}
			}
			m_Score += (m_Level + 1) * (linesCount >= 4 ? 800 : (linesCount * 100));
			m_Level = m_Lines / 10;
		}
		private void FallOneLine(int y)
		{
			for (int c = y - 1; c >= 0; c--)
			{
				for (int d = 0; d < m_Width; d++)
				{
					m_Matrix[d + (c + 1) * m_Width] = m_Matrix[d + c * m_Width];
					m_Matrix[d + c * m_Width] = null;
				}
			}
		}
	}
}
